Rvalue et lvalue **************** En C++, chaque expression appartient à une catégorie spécifique. A l'époque du C =============== Introduction ------------ Le langage C a déjà commencé à poser les bases d'une classification des expressions basée sur deux grandes catégories : les lvalues et les rvalues. Pour introduire ces notions, prenons un exemple : .. code-block:: cpp T[i] = 7; L'élément sur la gauche du signe =, n'est pas un nom de variable, nous n'avons pas écrit simplement *x=7*. L'écriture *T[i]* est plus complexe et correspond à une expression : un mélange de variables, de fonctions et d'opérateurs Ainsi, on trouve : * Un index *i* * Le nom d'un container *T* * L'utilisation de l'opérateur [] Toutes les expressions ne peuvent pas être placées à gauche d'une affectation. Par exemple, si l'on permutte l'écriture de notre premier exemple, nous avons un problème : .. code-block:: cpp 7 = T[i]; L'expression T[i], peut se placer à droite comme à gauche de l'affectation, par contre, la valeur 7 ne peut jamais se placer à gauche du signe =. Ainsi, le langage C opère une distinction entre les expressions qui peuvent apparaître à gauche d’une affectation et celles qui ne le peuvent pas. Nous obtenons donc les définitions de nos deux catégories d'expression : * **lvalue** (left) : une expression pouvant apparaître à gauche d’une affectation * **rvalue** (right) : une expression qui ne peut pas apparaître à gauche d'une affectation Quelques remarques : * Une lvalue peut se trouver à droite d'une affectation, cela n'en fait pas une rvalue pour autant !!! * On peut rencontrer des expressions complexes comme *T[3+i-k*L+f()]* qui correspond à une lvalue. * Une rvalue ne peut se trouver qu'à droite dans une affectation. Exemples de rvalue ------------------ .. code-block:: cpp // Numeric literals 5 3.14 // Results of arithmetic expressions int a = 2, b = 3; int c = a + b; // (a+b) is an rvalue // Value returned by a function int max(int v); int x = max(6); // max(6) is an rvalue // explicit cast (int) d // post-increment int n = 1; n++; // comparison operator a < b Exemples de lvalues ------------------- .. code-block:: cpp // Ordinary variable int x; x = ... // Array members T[3] = ... // Structure field struct Point { int x, y; }; Point p; p.x = ... Quizzz ====== .. quiz:: rvaluelvalue :title: lvalue et rvalue. Indiquez si chaque affirmation est vraie ou fausse : .. csv-table:: :widths: 10 :delim: ! :quiz:`{"type":"TF","answer":"F"}` Lvalue et rvalue correspondent à des types :quiz:`{"type":"TF","answer":"T"}` Une lvalue peut se situer à droite et à gauche dans une affectation :quiz:`{"type":"TF","answer":"T"}` Une rvalue peut se situer uniquement à droite dans une affectation :quiz:`{"type":"TF","answer":"F"}` Une lvalue se définit par le caractère éphémère du résultat qu'elle représente :quiz:`{"type":"TF","answer":"T"}` Une lvalue permet de stocker un résultat :quiz:`{"type":"TF","answer":"T"}` Si *T* est un tableau, l'expression *T[0]* désigne une lvalue :quiz:`{"type":"TF","answer":"F"}` L'expression *a+b* désigne une lvalue Catégories en C++ ================= L'arrivée du C++ ajoute deux éléments supplémentaires dans le langage par rapport au C : * Les références * Les objets (anonymes et nommés) Il faut garder à l’esprit que cette partie de la norme C++ est particulièrement délicate car d’une part les définitions ont évolué de C++11 à C++23 et d'autre part le langage ne propose pas de règles unificatrices simples, mais du cas par cas. Les explications données ici sont donc volontairement partielles, destinées à offrir au lecteur quelques points de repère. Un résumé parfaitement fidèle serait illusoire, et nos choix pédagogiques impliquent nécessairement une part de simplification. Introduction ------------ Le C++ a élargi la classification existante en C. Cependant, 99% du temps, deux catégories vont vous concerner : - Les *lvalues* : désignent une expression associée à un élément perenne en mémoire, similaire à la définition du C - Les *prvalues* (pure values) : valeur éphémère ou objet temporaire / anonyme, similaire à la définition des rvalues * Un littéral numérique : 5 * Le résultat d'un calcul : 5 + 7 * Un objet anonyme : Matrix() * Le résultat temporaire d'un opérateur : *Matrix1+Matrix2* Voici quelques exemples utilisant des prvalues atypiques : .. code-block:: cpp S(); // OK - prvalue (just a value, no persistent object) S() == S(); // OK - compare two prvalues, ok, similar to 3 == 5 S() = S(); // KO - a prvalue is not an assignable Les références représentent une nouvelle source de lvalues : .. code-block:: cpp int arr[3] = {1, 2, 3}; int& r = arr[1]; // reference to arr[1] r = 42; Voici un exemple d'une utilisation atypique : .. code-block:: cpp f() = 7; Sur la gauche de l'affectation, on trouve un appel de fonction. Pour savoir si ce code compile, il faut connaître ce que retourne *f* : * Si *f* retourne une lvalue, alors ce code va compiler. * Si *f* retourne une prvalue, alors ce code va déclencher une erreur. La catégorie des xvalues ------------------------ Le langage C++ a ajouté une catégorie supplémentaire pour compléter les deux précédentes : les *xvalues* (expiring values). Pourquoi ajouter cette catégorie qui va couvrir 1% des cas restants ? Ramenons-nous à la philosophie première du C++ : l'optimisation de la performance. Ainsi, lorsque vous écrivez le code suivant : .. code-block:: cpp Matrix M1,M2,M3; ... M1 = M2+M3; L'ordre d'évaluation des étapes est le suivant : * Appel de l'opérateur + prenant deux matrices *M2* et *M3* * Calcul du résultat de l'opérateur et retour d'un objet temporaire anonyme * Recopie (=) des valeurs de l'objet temporaire dans *M1* * Destruction de l'objet temporaire Ce fonctionnement est correct mais peu optimisé car la recopie des valeurs de l'objet temporaire vers M1 prend du temps, presque autant que le calcul de l'addition. Pour aller plus vite que la lumière, il aurait été plus pratique... de **piller** les données de l'objet temporaire pour les attribuer à *M1*. Est-ce grave ? Non, car l'objet temporaire part à la poubelle... donc à ce niveau, c'est tout bénéfice. Ainsi est créée la catégorie des **xvalues** qui correspondent à des lvalues/rvalues que l'on va marquer comme "pillables". Tout ne peut pas cependant être converti en *xvalues*, voici quelques exemples : * Pour les prvalues : 44 ou 4+7 ne sont pas des ressources pillables * Pour les lvalues : une const reference ne peut être pillée A noter que : * Les éléments dans le code qui sont, par nature, des *xvalues* sont rares (hors programme). * Pour obtenir des *xvalues* à partir d’expressions *lvalue* ou *prvalue*, il faut utiliser la nouvelle syntaxe du C++ && ou l'utilitaire *move* (hors programme). Quizzz ====== .. quiz:: Cppcat :title: Catégories Indiquez si chaque affirmation est vraie ou fausse : .. csv-table:: :widths: 10 :delim: ! :quiz:`{"type":"TF","answer":"T"}` Une lvalue permet de stocker un résultat :quiz:`{"type":"TF","answer":"T"}` Une prvalue ne peut pas être utilisée à gauche d’une affectation. :quiz:`{"type":"TF","answer":"F"}` L'écriture f() = 7; ne peut jamais compiler. :quiz:`{"type":"TF","answer":"T"}` Les xvalues ont été introduites pour optimiser les performances. :quiz:`{"type":"TF","answer":"F"}` Un littéral numérique représente une lvalue.